
/* PDT    - marks correlated to the REPORT file showing bug fixes and improvements */
using System;
using System.IO;
using System.Collections;
using System.Text;

namespace Taste {

public class Token {
	public int kind;    // token kind
	public int pos;     // token position in the source text (starting at 0)
	public int col;     // token column (starting at 0)
	public int line;    // token line (starting at 1)
	public string str;  // exact string value                                    /* PDT 42 */
	public string val;  // token value                                           /* PDT 42 */
}

public class Buffer {
	static byte[] buf;
	static int bufLen;
	static int pos;
	public const int eof = '\uffff';                                             /* PDT 16 */

	public static void Fill(string fileName) {
		try {
			FileStream s = new FileStream(fileName, FileMode.Open);
			bufLen = (int) s.Length;
			buf = new byte[bufLen];
			s.Read(buf, 0, bufLen); pos = 0;
		} catch (IOException) {
			Console.WriteLine("--- Cannot open file {0}", fileName);
			System.Environment.Exit(0);
		}
	}

	public static int Read() {
		if (pos < bufLen)
			return buf[pos++];
		else
			return eof;                                                              /* PDT 16 */
	}

	public static int Pos {
		get { return pos; }
		set {
			if (value < 0) pos = 0; else if (value >= bufLen) pos = bufLen; else pos = value;
		}
	}
}


public class Scanner {
	const char EOF = '\0';
	const char CR  = '\r';
	const char LF  = '\n';
	const int noSym = 29;
	static short[] start = {
	 13,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  8, 10,  0,  7,  4,  9,
	  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  5,  3, 11,  6, 12,  0,
	  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
	  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
	  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
	  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0};


	static Token t;          // current token
	static char strCh;       // current input character (original)               /* PDT 42 */
	static char ch;          // current input character
	static char lastCh;      // last input character
	static int pos;          // column number of current character
	static int line;         // line number of current character
	static int lineStart;    // start position of current line
	static Queue oldEols;    // EOLs that appeared in a comment;
	static BitArray ignore;  // set of characters to be ignored by the scanner

	public static void Init(String fileName) {
		Buffer.Fill(fileName);
		pos = -1; line = 1; lineStart = 0; lastCh = '\0';                          /* PDT 13 */
		oldEols = new Queue();
		NextCh();
		ignore = new BitArray(256);
		ignore[9] = true; ignore[10] = true; ignore[13] = true; ignore[32] = true; 
		
	}

	private static void NextCh() {
		if (oldEols.Count > 0) {
			ch = (char) oldEols.Dequeue();
		} else {
			lastCh = ch;
			strCh = (char) Buffer.Read(); pos++;                                     /* PDT 42 */
			ch = strCh;
			if (ch == '\uffff') ch = EOF;
			else if (ch == CR) {line++; lineStart = pos + 1;}
			else if (ch == LF) {
				if (lastCh != CR) line++;
				lineStart = pos + 1;
			} else if (ch > '\u00ff') {                                              /* PDT 15 */
				Errors.StoreError(0, line, pos - lineStart + 1, "bad character in source file");
				Errors.count++; ch = ' ';
			}
		}
	}


	static bool Comment0() {
		int level = 1, line0 = line, lineStart0 = lineStart;
		NextCh();
		if (ch == '*') {
			NextCh();
			for(;;) {
				if (ch == '*') {
					NextCh();
					if (ch == ')') {
						level--;
						if (level == 0) {
							while(line0 < line) {oldEols.Enqueue('\r'); oldEols.Enqueue('\n'); line0++;}
							NextCh(); return true;
						}
						NextCh();
					}
				} else if (ch == '(') {
					NextCh();
					if (ch == '*') {
						level++; NextCh();
					}
				} else if (ch == EOF) return false;
				else NextCh();
			}
		} else {
			if (ch == CR || ch == LF) {line--; lineStart = lineStart0;}
			pos = pos - 2; Buffer.Pos = pos+1; NextCh();
		}
		return false;
	}


	static void CheckLiteral() {                                                 /* PDT 42 */
		t.val = t.str;
		switch (t.val) {
			case "PROGRAM": t.kind = 3; break;
			case "VAR": t.kind = 6; break;
			case "PROCEDURE": t.kind = 8; break;
			case "BEGIN": t.kind = 9; break;
			case "END": t.kind = 10; break;
			case "INTEGER": t.kind = 11; break;
			case "BOOLEAN": t.kind = 12; break;
			case "IF": t.kind = 14; break;
			case "THEN": t.kind = 15; break;
			case "ELSE": t.kind = 16; break;
			case "WHILE": t.kind = 17; break;
			case "DO": t.kind = 18; break;
			case "READ": t.kind = 19; break;
			case "WRITE": t.kind = 20; break;
			case "TRUE": t.kind = 21; break;
			case "FALSE": t.kind = 22; break;
			default: break;

		}
	}

	public static Token Scan() {
		while (ignore[ch]) NextCh();
		if (ch == '(' && Comment0() ) return Scan();
		t = new Token();
		t.pos = pos; t.col = pos - lineStart + 1; t.line = line; 
		int state = start[ch];
		StringBuilder buf = new StringBuilder(16);
		buf.Append(strCh);                                                         /* PDT 42 */
		NextCh();
		switch (state) {
			case 0: {t.kind = noSym; goto done;} // NextCh already done
			case 1:
				if ((ch >= '0' && ch <= '9'
				  || ch >= 'A' && ch <= 'Z'
				  || ch >= 'a' && ch <= 'z')) {buf.Append(strCh); NextCh(); goto case 1;}
				else {t.kind = 1; t.str = buf.ToString(); CheckLiteral(); return t;}
			case 2:
				if ((ch >= '0' && ch <= '9')) {buf.Append(strCh); NextCh(); goto case 2;}
				else {t.kind = 2; goto done;}
			case 3:
				{t.kind = 4; goto done;}
			case 4:
				{t.kind = 5; goto done;}
			case 5:
				{t.kind = 7; goto done;}
			case 6:
				{t.kind = 13; goto done;}
			case 7:
				{t.kind = 23; goto done;}
			case 8:
				{t.kind = 24; goto done;}
			case 9:
				{t.kind = 25; goto done;}
			case 10:
				{t.kind = 26; goto done;}
			case 11:
				{t.kind = 27; goto done;}
			case 12:
				{t.kind = 28; goto done;}
			case 13:
				{t.kind = 0; goto done;}
		}
		done:
		t.str = buf.ToString();                                                    /* PDT 42 */
		t.val = t.str;
		return t;
	}

} // end Scanner

public delegate void ErrorProc(int n, int line, int col);                      /* PDT 40 */
public delegate void StoreProc(int n, int line, int col, string s);
public delegate void SummaryProc(string s);

public class Errors {
	public static int count = 0; // number of errors detected
	public static ErrorProc SynErr = new ErrorProc(DefaultError);  // syntactic errors
	public static ErrorProc SemErr = new ErrorProc(DefaultError);  // semantic errors
	public static StoreProc StoreError = new StoreProc(DefaultStoreError);
	public static SummaryProc Summarize = new SummaryProc(DefaultSummary);
	public static string fileName;
	
	public static void Exception(string s) {
		Console.WriteLine(s); System.Environment.Exit(0);
	}

	static void DefaultStoreError(int n, int line, int col, string s) {
		Console.WriteLine("{0} ({1} , {2}) {3}", fileName, line, col, s);
	}
	
	static void DefaultError(int n, int line, int col) {
		StoreError(n, line, col, "compiler error");
		count++;
	}
	
	static void DefaultSummary(string s) {
		switch (count) {
			case 0 : Console.WriteLine("No errors detected"); break;
			case 1 : Console.WriteLine("1 error detected"); break;
			default: Console.WriteLine(count + " errors detected"); break;
		}
	}
} // Errors

} // end namespace
